/**
 * GameTableWindow.java
 *
 * @author Beatrice Bacelli
 * @author Luca Bedogni
 * @author Silvia Righini
 */
package unibo.lsb.graphic;

import java.awt.Container;
import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SpringLayout;
import javax.swing.SwingUtilities;

import unibo.lsb.communication.InformationSingleton;
import unibo.lsb.logic.CoveredDice;
import unibo.lsb.logic.Dice;
import unibo.lsb.logic.LastAssignedPenalty;
import unibo.lsb.logic.Player;
import unibo.lsb.logic.PlayerId;
import unibo.lsb.logic.PlayersTable;
import unibo.lsb.server.Game;

/**
 * 
 */
public class GameTableWindow extends javax.swing.JFrame {

	int numberOfPlayers = 0;
	String gameName = "Game";
	String message = "Game started!";

	SpringLayout layout = new SpringLayout();
	java.util.List<Player> listOfPlayers = new java.util.ArrayList<Player>();
	CenteredBackgroundBorder bkgrnd;
	Dimension windowSize;
	List<UserPanel> listOfUserPanels = Collections
			.synchronizedList(new ArrayList<UserPanel>());

	Dice castDice;

	/**
	 * Creates new form Registration
	 * */
	public GameTableWindow() {
		prepareBkgrndAndSizes();
		initComponents();
	}

	/**
	 * Initialization
	 */
	private void initComponents() {
		setName(gameName);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setSize(windowSize);
		setResizable(false);
		setLocationRelativeTo(null);
		setVisible(true);

		final Container cp = getContentPane();
		((JPanel) cp).setBorder(bkgrnd);

		final PlayersTable playersTable = InformationSingleton.getInstance()
				.getpt();

		Runnable r = new Runnable() {

			public void run() {
				cp.repaint();
				refreshPanels(playersTable);
				createOrRefreshInformationPanel(message,
						ConfigurationConstGraphics.SET_TEXT, cp);
				cp.repaint();

			}
		};
		SwingUtilities.invokeLater(r);
	}

	/**
	 * Sets frame background and specifies frame size
	 */
	private void prepareBkgrndAndSizes() {
		BufferedImage bufferedImage = (new GraphicUtils())
				.getImage(ConfigurationConstGraphics.TABLE_IMG);
		windowSize = new Dimension(bufferedImage.getWidth(), bufferedImage
				.getHeight());
		bkgrnd = new CenteredBackgroundBorder(bufferedImage);
		windowSize = bkgrnd.getImageDimension();
	}

	/**
	 * Creates a brand new InformationPanel or retrieves the existing one. Then
	 * it sets the given message in the panel, appending it or replacing the
	 * previous one.
	 * 
	 * @param message
	 *            the message to add or set
	 * @param i
	 *            {@link ConfigurationConstGraphics#SET_TEXT} if the text
	 *            message must be set in the panel, overwriting any previous
	 *            message present, {@link ConfigurationConstGraphics#ADD_TEXT}
	 *            if it must be added to the previous message.
	 * @param c
	 *            the content pane
	 */
	private void createOrRefreshInformationPanel(String message, int i,
			Container c) {

		InformationPanel informationPanel = InformationPanelSingleton
				.getInstance().getInformationPanel();

		if (i == ConfigurationConstGraphics.SET_TEXT) {
			informationPanel.setMessage(message);
		} else {
			informationPanel.addMessage(message);
		}

		c.add(informationPanel);
		layout.putConstraint(SpringLayout.VERTICAL_CENTER, informationPanel, 0,
				SpringLayout.VERTICAL_CENTER, c);
		layout.putConstraint(SpringLayout.HORIZONTAL_CENTER, informationPanel,
				0, SpringLayout.HORIZONTAL_CENTER, c);
		c.setLayout(layout);
		informationPanel.revalidate();
		informationPanel.repaint();
	}

	/**
	 * Creates the list of the user panels, setting their sizes and positions.
	 * 
	 * @param n
	 * @param c
	 */
	private void createUserPanels(int n, Container c) {

		synchronized (listOfUserPanels) {
			List<UserPanel> l = new ArrayList<UserPanel>();

			if (listOfUserPanels.isEmpty()) {
				double distNsside = windowSize.getHeight() / 2;
				double distWlside = windowSize.getWidth() / 2;
				double distWsside = ConfigurationConstGraphics.PADDING_FROM_RIGHT;
				double distNlside = ConfigurationConstGraphics.PADDING_FROM_TOP;

				for (int count = 1; count <= numberOfPlayers; count++) {
					UserPanel up = new UserPanel();
					c.add(up);

					l.add(up);

					double w = up.getPreferredSize().getWidth() / 2;
					double h = up.getPreferredSize().getHeight() / 2;

					String direction1 = "";
					int distance1 = 0;
					String direction2 = "";
					int distance2 = 0;

					switch (count) {
					case 1:
						direction1 = SpringLayout.NORTH;
						distance1 = (int) (distNsside - h);
						direction2 = SpringLayout.WEST;
						distance2 = (int) distWsside;
						break;
					case 2:
						direction1 = SpringLayout.NORTH;
						distance1 = (int) (distNsside - h);
						direction2 = SpringLayout.EAST;
						distance2 = -(int) distWsside;
						break;
					case 3:
						direction1 = SpringLayout.NORTH;
						distance1 = (int) distNlside;
						direction2 = SpringLayout.WEST;
						distance2 = (int) (distWlside - w);
						break;
					case 4:
						direction1 = SpringLayout.SOUTH;
						distance1 = -(int) distNlside;
						direction2 = SpringLayout.WEST;
						distance2 = (int) (distWlside - w);
						break;
					case 5:
						direction1 = SpringLayout.NORTH;
						distance1 = (int) distNlside;
						direction2 = SpringLayout.WEST;
						distance2 = (int) (distWlside / 2 - w);
						break;
					case 6:
						direction1 = SpringLayout.SOUTH;
						distance1 = -(int) distNlside;
						direction2 = SpringLayout.EAST;
						distance2 = -(int) (distWlside / 2 - w);
						break;
					case 7:
						direction1 = SpringLayout.NORTH;
						distance1 = (int) distNlside;
						direction2 = SpringLayout.EAST;
						distance2 = -(int) (distWlside / 2 - w);
						break;
					case 8:
						direction1 = SpringLayout.SOUTH;
						distance1 = -(int) distNlside;
						direction2 = SpringLayout.WEST;
						distance2 = (int) (distWlside / 2 - w);
						break;
					}
					// System.err.println("Number " + count);
					// System.err.println(direction1 + " " + distance1);
					// System.err.println(direction2 + " " + distance2);
					layout.putConstraint(direction1, up, distance1, direction1,
							c);
					layout.putConstraint(direction2, up, distance2, direction2,
							c);
				}
				c.setLayout(layout);

				// Setting order
				listOfUserPanels.add(l.get(0));
				switch (numberOfPlayers) {
				case 2:
					listOfUserPanels.add(l.get(1));
					break;
				case 3:
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(1));
					break;
				case 4:
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(1));
					listOfUserPanels.add(l.get(3));
					break;
				case 5:
					listOfUserPanels.add(l.get(4));
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(1));
					listOfUserPanels.add(l.get(3));
					break;
				case 6:
					listOfUserPanels.add(l.get(4));
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(1));
					listOfUserPanels.add(l.get(5));
					listOfUserPanels.add(l.get(3));
					break;
				case 7:
					listOfUserPanels.add(l.get(4));
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(6));
					listOfUserPanels.add(l.get(1));
					listOfUserPanels.add(l.get(5));
					listOfUserPanels.add(l.get(3));
					break;
				case 8:
					listOfUserPanels.add(l.get(4));
					listOfUserPanels.add(l.get(2));
					listOfUserPanels.add(l.get(6));
					listOfUserPanels.add(l.get(1));
					listOfUserPanels.add(l.get(5));
					listOfUserPanels.add(l.get(3));
					listOfUserPanels.add(l.get(7));
					break;
				}

				Iterator<UserPanel> iup = listOfUserPanels.iterator();
				for (int pCount = 0; pCount <= numberOfPlayers - 1; pCount++) {
					UserPanel up = iup.next();
					updateSinglePanel(up, listOfPlayers.get(pCount));
					up.revalidate();
					up.repaint();
				}
			}

		}
	}

	private UserPanel createSinglePanel(Container c) {
		UserPanel userPanel = new UserPanel();
		c.add(userPanel);
		userPanel.revalidate();
		userPanel.repaint();
		return userPanel;
	}

	private void updateSinglePanel(UserPanel p, Player player) {
		p.updatePanel(player);
	}

	private void updateSinglePanel(UserPanel p) {
		p.updatePanel();
	}

	public void setMessage(String message) {
		this.message = message;

		Container cp = this.getContentPane();
		cp.repaint();
		createOrRefreshInformationPanel(message,
				ConfigurationConstGraphics.SET_TEXT, cp);
	}

	public void addMessage(String message) {
		this.message = message;

		Container cp = this.getContentPane();
		cp.repaint();
		createOrRefreshInformationPanel(message,
				ConfigurationConstGraphics.ADD_TEXT, cp);
	}

	public PlayersTable setGame(Game game) {
		this.numberOfPlayers = game.getNumberOfPlayers();
		this.gameName = game.getName();
		PlayersTable pt = new PlayersTable(game.getListOfPlayers());
		this.listOfPlayers = pt.getPlayers();
		createUserPanels(listOfPlayers.size(), this.getContentPane());
		return pt;
	}

	/**
	 * Turn must be between 0 and numberOfPlayers - 1
	 */
	public void setTurnToPlayer(int i) {
		synchronized (listOfUserPanels) {
			unsetTurnAll();
			if ((i >= 0) || (i <= listOfUserPanels.size())) {
				listOfUserPanels.get(i).setTurnToPlayer();
				updateSinglePanel(listOfUserPanels.get(i));
			}
		}
	}

	private void unsetTurnAll() {
		synchronized (listOfUserPanels) {
			Iterator<UserPanel> ite = listOfUserPanels.iterator();
			while (ite.hasNext()) {
				ite.next().unsetTurnToPlayer();
			}
		}
	}

	public void refreshPanels(PlayersTable t) {
		synchronized (listOfUserPanels) {
			listOfPlayers = t.getPlayers();
			refreshPanels();
		}
	}

	public void refreshPanels() {
		Container cp = this.getContentPane();
		cp.repaint();
		synchronized (listOfUserPanels) {
			Iterator<UserPanel> ite = listOfUserPanels.iterator();
			Iterator<Player> iteP = listOfPlayers.iterator();
			while (ite.hasNext() && iteP.hasNext()) {
				updateSinglePanel(ite.next(), iteP.next());
			}
		}
	}

	/**
	 * 
	 * @param p
	 * @param coveredDice
	 * @param lastAssignedPenalty
	 * @param playerWhoGetsPenalty
	 */
	public void showPenaltyForDoubtDice(PlayerId p, CoveredDice coveredDice,
			LastAssignedPenalty lastAssignedPenalty, int playerWhoGetsPenalty) {

		this.addMessage(MessagesGenerator.xDeclaredDiceButMadeOtherDice(
				MessagesGenerator.PLAYER_OTHER, coveredDice));
		this.addMessage(MessagesGenerator.penaltyForDoubting(
				playerWhoGetsPenalty, lastAssignedPenalty));

		DoubtOthersDiceDialog dialog = new DoubtOthersDiceDialog(this, true,
				coveredDice, lastAssignedPenalty, playerWhoGetsPenalty);
		dialog.setVisible(true);
	}

	public void showYourPenalty(int penalty) {
		String message = MessagesGenerator.youGetPenalty(penalty);
		addMessage(MessagesGenerator.youGetPenalty(penalty));
		JOptionPane.showMessageDialog(this, message, "Penalty!",
				JOptionPane.PLAIN_MESSAGE);
	}

	/**
	 * 
	 * @param p
	 *            player who cast the dice
	 * @param dependingDice
	 * @return
	 */
	public CoveredDice castDice(Player p, Dice dependingDice) {
		CastDiceDialog dialog = new CastDiceDialog(this, true, dependingDice);
		dialog.setVisible(true);

		CoveredDice d = dialog.getCoveredDice(p.getPlayerId());
		return d;
	}

	public CoveredDice castDice(PlayerId p, Dice dependingDice) {
		CastDiceDialog dialog = new CastDiceDialog(this, true, dependingDice);
		dialog.setVisible(true);
		CoveredDice d = dialog.getCoveredDice(p);
		if (d == null) {
			System.err.println("Dice null");
		}
		return d;
	}

	/**
	 * 
	 * @param p
	 * @param d
	 *            declared dice
	 * @return one among {@link ConfigurationConstGraphics#DICE_ACCEPTED},
	 *         {@link ConfigurationConstGraphics#DICE_21ACCEPTED},
	 *         {@link ConfigurationConstGraphics#DICE_DOUBTED}
	 */
	public int showAcceptOrDoubtDialog(PlayerId p, Dice d) {
		String message = MessagesGenerator.playerDeclaresDiceMessage(p, d);
		return showAcceptOrDoubtDialog(message, d);

	}

	/**
	 * 
	 * @param msg
	 * @param n
	 *            is 0 if normal dice, 1 if 21
	 * @return one among {@link ConfigurationConstGraphics#DICE_ACCEPTED},
	 *         {@link ConfigurationConstGraphics#DICE_21ACCEPTED},
	 *         {@link ConfigurationConstGraphics#DICE_DOUBTED}
	 */
	private int showAcceptOrDoubtDialog(String msg, Dice d) {
		int returnValue = ConfigurationConstGraphics.DICE_ACCEPTED;
		String yesValue = "Accept and cast again";
		String noValue = "Doubt it!";
		String yes21Value = "Just accept it";

		Object[] options = new String[2];
		if (d.is21()) {
			options[0] = yes21Value;
		} else {
			options[0] = yesValue;
		}
		options[1] = noValue;
		final JOptionPane optionPane = new JOptionPane(msg,
				JOptionPane.PLAIN_MESSAGE, JOptionPane.YES_NO_OPTION, null,
				options, options[0]);

		final JDialog dialog = new JDialog(this, "Accept or doubt", true);
		dialog.setContentPane(optionPane);
		dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);

		optionPane.addPropertyChangeListener(new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent e) {
				String prop = e.getPropertyName();
				if (dialog.isVisible() && (e.getSource() == optionPane)
						&& (JOptionPane.VALUE_PROPERTY.equals(prop))) {
					dialog.setVisible(false);
				}
			}
		});

		dialog.pack();
		dialog.setLocationRelativeTo(this);
		dialog.setVisible(true);

		String valueReturned = (String) optionPane.getValue();
		if (valueReturned.equals(yesValue)) {
			returnValue = ConfigurationConstGraphics.DICE_ACCEPTED;
		} else if (valueReturned.equals(noValue)) {
			returnValue = ConfigurationConstGraphics.DICE_DOUBTED;
		} else if (valueReturned.equals(yes21Value)) {
			returnValue = ConfigurationConstGraphics.DICE_21ACCEPTED;
		} else {
			System.err
					.println("Weird return value of accept or doubt dialog box.");
		}
		return returnValue;
	}

	/**
	 * Modifies a {@link UserPanel} to show a player is dead.
	 * 
	 * @param i
	 *            the index of the player in the {@link PlayersTable}
	 */
	public void setDead(int i) {
		synchronized (listOfUserPanels) {
			if ((i >= 0) || (i <= listOfUserPanels.size())) {
				listOfUserPanels.get(i).setDead();
				updateSinglePanel(listOfUserPanels.get(i));
			}
		}
	}

	/**
	 * Modifies a {@link UserPanel} to show a player reached
	 * {@link PlayersTable#MAX_PENALTIES} and is now out of the game.
	 * 
	 * @param i
	 *            the index of the player in the {@link PlayersTable}
	 */
	public void setLost(int i) {
		synchronized (listOfUserPanels) {
			if ((i >= 0) || (i <= listOfUserPanels.size())) {
				listOfUserPanels.get(i).setLost();
				updateSinglePanel(listOfUserPanels.get(i));
			}
		}
	}

	public void close() {
		MainWindowSingleton.getInstance().getMainWindow().setVisible(true);
		this.setVisible(false);
		this.dispose();
		System.exit(0);
	}
}
